package org.domus.sipnmove.useragent.impl;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.URI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.domus.sipnmove.useragent.Call;
import org.domus.sipnmove.useragent.CallObserver;
import org.domus.sipnmove.useragent.UserAgent;
import org.domus.sipnmove.useragent.UserInformation;

/**
 * Simple implementation for a call
 */
public class CallImpl implements Call {

	private UserAgent userAgent;
	private AddressFactory addressFactory;
	private HeaderFactory headerFactory;
	private MessageFactory messageFactory;
	private SipProvider sipProvider;
	private String callId;
	private Request inviteRequest;

	public CallImpl(UserAgent userAgent) {
		this.userAgent = userAgent;
	}

	@Override
	public void addCallObserver(CallObserver callObserver) {
	}

	@Override
	public void terminate() {
		if (!callId.equals(null))
			userAgent.removeCall(callId);
	}

	@Override
	public void initiate(String toAddressString) {

		UserInformation userInfos = userAgent.getUserInformation();

		try {

			// create From Header
			Address fromAddress = addressFactory.createAddress(userInfos.getSipAddress());
			FromHeader fromHeader = headerFactory.createFromHeader(fromAddress, "12345");

			// create To Header
			Address toAddress = addressFactory.createAddress(toAddressString);
			ToHeader toHeader = headerFactory.createToHeader(toAddress, null);

			// create Request URI
			URI requestURI = addressFactory.createAddress(toAddressString).getURI();

			// Create ViaHeaders
			List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
			ViaHeader viaHeader = headerFactory.createViaHeader(userInfos.getIpAddress(), userInfos.getPort(), "udp", null);
			viaHeaders.add(viaHeader);

			// Create ContentTypeHeader
			ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "sdp");

			// Create a new CallId header
			CallIdHeader callIdHeader = sipProvider.getNewCallId();
			callId = callIdHeader.getCallId();

			// Create a new Cseq header
			CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.INVITE);

			// Create a new MaxForwardsHeader
			MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);

			// Create the request.
			Request request = messageFactory.createRequest(requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards);

			// Create contact headers
			Address contactAddress = addressFactory.createAddress(userInfos.getSipAddress());
			request.addHeader(headerFactory.createContactHeader(contactAddress));

			// You can add extension headers of your own making
			// to the outgoing SIP request.
			// Add the extension header.
			// Header extensionHeader = headerFactory.createHeader("My-Header",
			// "my header value");
			// request.addHeader(extensionHeader);

			// This is an example SDP content, but it will not be parsed in our
			// case because of time

			String sdpData = "v=0\r\n" + "o=4855 13760799956958020 13760799956958020" + " IN IP4  129.6.55.78\r\n" + "s=mysession session\r\n" + "p=+46 8 52018010\r\n" + "c=IN IP4  129.6.55.78\r\n" + "t=0 0\r\n" + "m=audio 6022 RTP/AVP 0 4 18\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" + "a=rtpmap:18 G729A/8000\r\n" + "a=ptime:20\r\n";
			byte[] contents = sdpData.getBytes();
			request.setContent(contents, contentTypeHeader);

			// Create the client transaction and send the request
			ClientTransaction clientTransaction = sipProvider.getNewClientTransaction(request);
			clientTransaction.sendRequest();

			// dialog = clientTransaction.getDialog();

		} catch (SipException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		} catch (InvalidArgumentException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @return the addressFactory
	 */
	public AddressFactory getAddressFactory() {
		return addressFactory;
	}

	/**
	 * Return the call id
	 */
	public String getCallId() {
		return callId;
	}

	/**
	 * Process the ACK request. Send the bye and complete the call flow.
	 */
	public void processAck(RequestEvent requestEvent, ServerTransaction serverTransaction) {

		// TODO Initiate Socket

	}

	public void processInvite(RequestEvent requestEvent, ServerTransaction serverTransaction) {
		SipProvider sipProvider = (SipProvider) requestEvent.getSource();
		Request request = requestEvent.getRequest();
		UserInformation userInfos = userAgent.getUserInformation();

		inviteRequest = request;
		
		try {

			if (serverTransaction == null) {
				serverTransaction = sipProvider.getNewServerTransaction(requestEvent.getRequest());
			}

			Response okResponse = messageFactory.createResponse(Response.OK, request);
			Address address = addressFactory.createAddress(userInfos.getSipAddress());
			ContactHeader contactHeader = headerFactory.createContactHeader(address);
			ToHeader toHeader = (ToHeader) okResponse.getHeader(ToHeader.NAME);
			toHeader.setTag("4321");
			okResponse.addHeader(contactHeader);

			serverTransaction.sendResponse(okResponse);

		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(0);
		}
	}
 
	/**
	 * Process the bye request.
	 */
	public void processBye(RequestEvent requestEvent, ServerTransaction serverTransactionId) {
		SipProvider sipProvider = (SipProvider) requestEvent.getSource();
		Request request = requestEvent.getRequest();
		Dialog dialog = requestEvent.getDialog();
		
		try {
			
			Response response = messageFactory.createResponse(200, request);
			serverTransactionId.sendResponse(response);
			

		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(0);

		}
	}

	public void processCancel(RequestEvent requestEvent, ServerTransaction serverTransactionId) {
		Request request = requestEvent.getRequest();
		Dialog dialog = requestEvent.getDialog();
		
		try {
			if (serverTransactionId == null) {
				
				return;
			}
			Response response = messageFactory.createResponse(200, request);
			serverTransactionId.sendResponse(response);
			if (dialog.getState() != DialogState.CONFIRMED) {
				response = messageFactory.createResponse(Response.REQUEST_TERMINATED, inviteRequest);
				requestEvent.getServerTransaction().sendResponse(response);
			}

		} catch (Exception ex) {
			ex.printStackTrace();
			System.exit(0);

		}
	}

	@Override
	public void processRequest(RequestEvent requestEvent, ServerTransaction serverTransaction) {
		Request request = requestEvent.getRequest();
		
		if (request.getMethod().equals(Request.INVITE)) {
			processInvite(requestEvent, serverTransaction);
		} else if (request.getMethod().equals(Request.ACK)) {
			processAck(requestEvent, serverTransaction);
		} else if (request.getMethod().equals(Request.BYE)) {
			processBye(requestEvent, serverTransaction);
		} else if (request.getMethod().equals(Request.CANCEL)) {
			processCancel(requestEvent, serverTransaction);
		} else {
			System.err.println("Unknown method");
		}

	}

	@Override
	public void processResponse(ResponseEvent responseEvent) {

		Response response = responseEvent.getResponse();
		CSeqHeader cSeqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
		Dialog dialog = responseEvent.getDialog();

		if (response.getStatusCode() == Response.OK) {
			if (cSeqHeader.getMethod().equals(Request.INVITE)) {
				try {
					Request ackRequest = dialog.createAck(((CSeqHeader) cSeqHeader).getSeqNumber());
					dialog.sendAck(ackRequest);

				} catch (SipException e) {
					e.printStackTrace();
				} catch (InvalidArgumentException e) {
					e.printStackTrace();
				}

			} else if (cSeqHeader.getMethod().equals(Request.CANCEL)) {
				if (dialog.getState() == DialogState.CONFIRMED) {
					try {

						Request byeRequest = dialog.createRequest(Request.BYE);

						ClientTransaction ct = sipProvider.getNewClientTransaction(byeRequest);
						dialog.sendRequest(ct);

					} catch (SipException e) {
						e.printStackTrace();
					}

				}

			}
		}
	}

	/**
	 * @param addressFactory
	 *            the addressFactory to set
	 */
	public void setAddressFactory(AddressFactory addressFactory) {
		this.addressFactory = addressFactory;
	}

	/**
	 * @return the headerFactory
	 */
	public HeaderFactory getHeaderFactory() {
		return headerFactory;
	}

	/**
	 * @param headerFactory
	 *            the headerFactory to set
	 */
	public void setHeaderFactory(HeaderFactory headerFactory) {
		this.headerFactory = headerFactory;
	}

	/**
	 * @return the messageFactory
	 */
	public MessageFactory getMessageFactory() {
		return messageFactory;
	}

	/**
	 * @param messageFactory
	 *            the messageFactory to set
	 */
	public void setMessageFactory(MessageFactory messageFactory) {
		this.messageFactory = messageFactory;
	}

	/**
	 * @return the sipProvider
	 */
	public SipProvider getSipProvider() {
		return sipProvider;
	}

	/**
	 * @param sipProvider
	 *            the sipProvider to set
	 */
	public void setSipProvider(SipProvider sipProvider) {
		this.sipProvider = sipProvider;
	}

}
